#include "generate_declaration.h"
#include "exceptions.h"

#include <sstream>

string generateDeclarations(ParseTree::Name ns, vector<ParseTree::Declaration> &decls)
{
    string c = "";
    for(unsigned int i = 0; i < decls.size(); i++)
        c += generateDeclaration(ns, decls[i]);
    return c;
}

// TODO flag to ignore return type (void oid)
ParseTree::Name findOverload(ParseTree::Name ns, ParseTree::Name name, ParseTree::Declaration &decl)
{
    ParseTree::Name origName = name;
    //std::cout << "\nfinding overload: " << printName(name) << ", " << decl.identifier.identifier << std::endl;

    // fix the decl type, because it probably is not a FQN
    if (decl.type.typeType == ParseTree::Type::UserType) {
        decl.type.userType = namespaceLookupFQN(ns, decl.type.userType);
        //std::cout << "usertype fixed to: " << printName(decl.type.userType) << std::endl;
    }
    
    //std::cout << "decl.type = " << printType(decl.type)<< std::endl;
    
    
    //std::cout << printSymbolTable(symbolTable) << std::endl;;
    
    //name.push_back(ParseTree::Identifier("%0"));
    if (symbolTable.find(name) != symbolTable.end())
    {
        //std::cout << "Find overload of function " << printName(name) << std::endl;
        //std::cout << "return type: " << printType(decl.type)<<std::endl;
        // this *could* be a call to an overload so compare the types
        // generate a list of possible symbols
        vector<Symbol> symbols;
        symbols.push_back(symbolTable[name]);
        name.push_back(ParseTree::Identifier("%0"));
        int n = 1;
        while (symbolTable.find(name) != symbolTable.end())
        {   
            symbols.push_back(symbolTable[name]);
            //std::cout << "adding symbol (" << printName(name) << ") => " << printSymbol(symbolTable[name]) << std::endl;
            std::stringstream out; out << n++;
            name.pop_back();
            name.push_back(ParseTree::Identifier("%" + out.str()));
        }
                                        
        // compare types
        bool permissive = false;
        retryPermissive:
        bool match = false;
        for (unsigned int i = 0; i < symbols.size(); i++)
        {
            //std::cout << "checking (" << i << ") " << printType(symbols[i].type) << " == " << printType(decl.type);            
            //std::cout << ", argument count: " << symbols[i].argumentTypes.size() << " == " << decl.formPars.size() << std::endl;
            //std::cout << "checking (" << i << ") symbol: " << printName(findSymbolName(symbols[i])) << ": " << printSymbol(symbols[i]) << std::endl;
            // if the return type is unknown, all return types are considered OK.
            if ((decl.type == ParseTree::Type::Unknown() || symbols[i].type == decl.type) &&
                symbols[i].argumentTypes.size() == decl.formPars.size())
            {
                //std::cout << "found same return type" << std::endl;
                match = true;
                for (unsigned int j = 0; j < symbols[i].argumentTypes.size(); j++)
                {
                    if (symbols[i].argumentTypes[j].typeType == ParseTree::Type::UserType)
                        symbols[i].argumentTypes[j].userType = namespaceLookupFQN(ns, symbols[i].argumentTypes[j].userType);
                    if (decl.formPars[j].type.typeType == ParseTree::Type::UserType)
                        decl.formPars[j].type.userType = namespaceLookupFQN(ns, decl.formPars[j].type.userType);
                    if (symbols[i].argumentTypes[j] != decl.formPars[j].type)
                    {
                        if (permissive)
                        {
                            // allow implicit casts to objects
                            if (decl.formPars[j].type.typeType == ParseTree::Type::PrimitiveType)
                            {
                                if (symbols[i].argumentTypes[j].userType == toName("Thinder.Prim.Integer")
                                    && decl.formPars[j].type.primitiveType == ParseTree::Type::Int)
                                {
                                    // found int that is castable to Integer to use this method/function
                                    continue;
                                }
                            }
                        }
                        //std::cout << "    " << printType(symbols[i].argumentTypes[j]) << " != " << printType(decl.formPars[j].type) << std::endl;
                        match = false;
                    }
                }
                if (match) // if there still is a match
                {
                    name = findSymbolName(symbols[i]);
                    break;
                }
            }
        }    
        
        if (!match)
        {
            if (!permissive) 
            {
                // retry in permissive mode (allow implicit casts to objects)
                //std::cout << "retry permissive..." << std::endl;
                permissive = true;
                goto retryPermissive;
            }
            
            string s = "";
            s += "Could not find overload `";
            s += printType(decl.type);
            s += " ";
            s += printName(origName);
            s += "(";
            for (unsigned int i = 0; i < decl.formPars.size(); i++)
            {
                s += printType(decl.formPars[i].type);
                if (i < decl.formPars.size() - 1)
                    s += ", ";
            }
            s += ")'";
            TException up(19, s);
            throw up;
            
        }
        if (permissive)
        {
            // if this overload was found using permissive mode, convert the 
            // nessesary arguments to the objects
            //std::cout << "permissive: " << printName(name) << std::endl;
            Symbol selectedSymbol = symbolTable[name];
                        
            for (unsigned int i = 0; i < selectedSymbol.argumentTypes.size(); i++)
            {
                decl.formPars[i].type = selectedSymbol.argumentTypes[i];
            }
        }
        
    } else {
        name.pop_back();
    }
    
    //std::cout << "found name: " << printName(name) << std::endl;
    return name;
}

ParseTree::Name findOverload(ParseTree::Name ns, ParseTree::Name name, ParseTree::ClassDeclaration &decl)
{
    if (decl.declType == ParseTree::ClassDeclaration::FieldMeth)
        return findOverload(ns, name, *decl.declaration);
    else if (decl.declType == ParseTree::ClassDeclaration::Constructor)
    {
        // convert to Declaration
        ParseTree::Declaration declaration;
        declaration.declType = ParseTree::Declaration::FunctionDecl;
        ParseTree::Type type;
        type.typeType = ParseTree::Type::UserType;
        type.userType = ParseTree::Name(1, decl.identifier);
        declaration.type = type;
        declaration.modifiers = decl.modifiers;
        declaration.identifier = decl.identifier;
        declaration.formPars = decl.formPars;
        declaration.statement = decl.statement;
        return findOverload(ns, name, declaration);
    } else 
    {
        TException up(20, "Overload other than function, method or constructor");
        throw up;
    }
    
}

bool isPublic(ParseTree::Declaration &decl)
{
    for (unsigned int i = 0; i < decl.modifiers.size(); i++)
        if (decl.modifiers[i] == ParseTree::Public)
            return true;
    return false;
}

bool isPublic(ParseTree::ClassDeclaration &decl)
{
    if (decl.declType == ParseTree::ClassDeclaration::Constructor)
    {
        for (unsigned int i = 0; i < decl.modifiers.size(); i++)
            if (decl.modifiers[i] == ParseTree::Public)
                return true;
        return false;
    } else
        return isPublic(*decl.declaration);
}

string generateDeclaration(ParseTree::Name ns, ParseTree::Declaration &decl)
{
    string init = "";
    string c = "";
    ParseTree::Type primInt;
    primInt.typeType =  ParseTree::Type::PrimitiveType;
    primInt.primitiveType = ParseTree::Type::Int;
    
    if (decl.declType == ParseTree::Declaration::VarDecl)
    {
        variables += generateType(ns, decl.type) + "* ";
        for (unsigned int i = 0; i < decl.vars.size(); i++)
        {
            ParseTree::Name name = ns;
            name.push_back(decl.vars[i].identifier);
            variables += symbolTable[name].internal;
            
            if (i < decl.vars.size() - 1)
            {
                variables += ", *";
            }
            // TODO uninitialized arrays
            c += symbolTable[name].internal + " = malloc(";
            for (unsigned int j = 0; j < symbolTable[name].type.arrayExpr.size(); j++)
                c += "*(" + generateExpression(ns, symbolTable[name].type.arrayExpr[j], primInt) + ") * ";
            c += "sizeof(" + generateType(ns, symbolTable[name].type) + "));\n";
            
            if (symbolTable[name].type.typeType == ParseTree::Type::UserType)
                freeVars[ns] += generateType(ns, symbolTable[name].type) + "_destroy(" + symbolTable[name].internal + ");\n";
            freeVars[ns] += "free(" + symbolTable[name].internal + ");\n";
            // Initialization
            if (decl.vars[i].init)
            {
                if (symbolTable[name].type.typeType == ParseTree::Type::PrimitiveType)
                {
                    c += "*" + symbolTable[name].internal + " = *(" + generateExpression(ns, decl.vars[i].expression, symbolTable[name].type) + ");\n";
                }
                else
                {
                    c += symbolTable[name].internal + " = " + generateExpression(ns, decl.vars[i].expression, symbolTable[name].type) + ";\n";
                }
                
            } else {
                if (symbolTable[name].type.typeType == ParseTree::Type::PrimitiveType)
                {
                    switch (symbolTable[name].type.primitiveType)
                    {
                        case ParseTree::Type::Int:
                        case ParseTree::Type::Float:
                        case ParseTree::Type::Bool:
                        case ParseTree::Type::Byte:
                        case ParseTree::Type::Short:
                        case ParseTree::Type::Char:
                        case ParseTree::Type::Long:
                        case ParseTree::Type::Double:
                            c += "*" + symbolTable[name].internal + " = 0;\n";
                            break;
                    }
                } else {
                    // No initialization! This is a pointer.
                    //c += "" + symbolTable[name].internal + " = " + generateType(ns, symbolTable[name].type) + "_init;\n";
                }
            }
            
            
        }
        variables += ";\n";
    } else if (decl.declType == ParseTree::Declaration::FunctionDecl)
    {
        ParseTree::Name name = ns;
        name.push_back(decl.identifier);
        name = findOverload(ns, name, decl);
        functionStack.push_back(name);
        functions[name] += generateType(ns, decl.type) + "* " + symbolTable[name].internal + "(" + generateFormPars(name, decl.formPars) + ") {\n";
        functions[name] += generateStatement(true, name, decl.statement);
        functions[name] += "}\n";
        prototypes += generateType(ns, decl.type) + "* " + symbolTable[name].internal + "(" + generateFormPars(name, decl.formPars) + ");\n";
        functionStack.pop_back();
    } else if (decl.declType == ParseTree::Declaration::ClassDecl)
    {
        ParseTree::Name name = ns;
        name.push_back(decl.identifier);
        // Init function for fields:
        ParseTree::Name initfunc = ns;
        initfunc.push_back(ParseTree::Identifier(decl.identifier.identifier + "_init"));
        ParseTree::Name destroyfunc = ns;
        destroyfunc.push_back(ParseTree::Identifier(decl.identifier.identifier + "_destroy"));
        
        functions[initfunc] = "void " + symbolTable[name].internal + "_init(" + symbolTable[name].internal + "* this){\n";
        functions[destroyfunc] = "void " + symbolTable[name].internal + "_destroy(" + symbolTable[name].internal + "* this){\n";
        classes[name] = "struct " + symbolTable[name].internal + "s {\n";
        classes[name] += generateClassDeclarations(name, decl.classdecls);
        classes[name] += "};";
        classprototypes += "typedef struct " + symbolTable[name].internal + "s " + symbolTable[name].internal + ";\n";
        
        prototypes += "void " + symbolTable[name].internal + "_init(" + symbolTable[name].internal + "* this);\n";
        prototypes += "void " + symbolTable[name].internal + "_destroy(" + symbolTable[name].internal + "* this);\n";
        functions[initfunc] += "}";
        functions[destroyfunc] += "}";
    } 
    
    return c;
}

string generateClassDeclarations(ParseTree::Name ns, vector<ParseTree::ClassDeclaration> &decls)
{
    string c = "";
    for(unsigned int i = 0; i < decls.size(); i++)
        c += generateClassDeclaration(ns, decls[i]);
    return c;
}

string generateClassDeclaration(ParseTree::Name ns, ParseTree::ClassDeclaration &decl)
{
    string c = "";
    string init = ""; 
    string destroy = "";
    if (decl.declType == ParseTree::ClassDeclaration::FieldMeth)
    {
        if (decl.declaration->declType == ParseTree::Declaration::VarDecl)
        {
            // Field
            c += generateType(ns, decl.declaration->type) + "* ";
            for (unsigned int i = 0; i < decl.declaration->vars.size(); i++)
            {
                ParseTree::Name name = ns;
                name.push_back(decl.declaration->vars[i].identifier);
                c += symbolTable[name].internal;
                if (i < decl.declaration->vars.size() - 1)
                    c += ", ";
                
                init += "this->" + symbolTable[name].internal + " = malloc(sizeof(" + generateType(ns, symbolTable[name].type) + "));\n";
                destroy += "free(this->" + symbolTable[name].internal + ");\n";
                
                // Initialization
                if (decl.declaration->vars[i].init)
                {
                    if (symbolTable[name].type.typeType == ParseTree::Type::PrimitiveType)
                    {
                        init += "*this->" + symbolTable[name].internal + " = *(" + generateExpression(ns, decl.declaration->vars[i].expression, symbolTable[name].type) + ");\n";
                    }
                    else
                    {
                        init += "this->" + symbolTable[name].internal + " = " + generateExpression(ns, decl.declaration->vars[i].expression, symbolTable[name].type) + ";\n";
                    }
                    
                } else {
                    if (symbolTable[name].type.typeType == ParseTree::Type::PrimitiveType)
                    {
                        switch (symbolTable[name].type.primitiveType)
                        {
                            case ParseTree::Type::Int:
                            case ParseTree::Type::Float:
                            case ParseTree::Type::Bool:
                            case ParseTree::Type::Byte:
                            case ParseTree::Type::Short:
                            case ParseTree::Type::Char:
                            case ParseTree::Type::Long:
                            case ParseTree::Type::Double:
                                init += "*this->" + symbolTable[name].internal + " = 0;\n";
                                break;
                        }
                    } else {
                        // No initialization! This is a pointer.
                        //c += "*" + symbolTable[name].internal + " = " + generateType(ns, symbolTable[name].type) + "_init;\n";
                    }
                }
                
            }
            c += ";\n";
        } else if (decl.declaration->declType == ParseTree::Declaration::FunctionDecl)
        {
            ParseTree::Name name = ns;
            name.push_back(decl.declaration->identifier);          
            name = findOverload(ns, name, decl);
            functionStack.push_back(name);
            functions[name]  = generateType(ns, decl.declaration->type) + "* " + symbolTable[name].internal + "(" + symbolTable[ns].internal + "* this";
            functions[name] += decl.declaration->formPars.size() > 0 ? ", " : "";
            functions[name] += generateFormPars(name, decl.declaration->formPars) + ") {\n";
            functions[name] += generateStatement(true, name, decl.declaration->statement);
            functions[name] += "}";
            functionStack.pop_back();

            prototypes += generateType(ns, decl.declaration->type) + "* " + symbolTable[name].internal + "(" + symbolTable[ns].internal + "* this";
            prototypes += decl.declaration->formPars.size() > 0 ? ", " : "";
            prototypes += generateFormPars(name, decl.declaration->formPars) + ");\n";
        } else {
            generateDeclaration(ns, *decl.declaration);
        }
    } else if (decl.declType == ParseTree::ClassDeclaration::Constructor)
    {
        ParseTree::Name name = ns;
        name.push_back(decl.identifier);
        name = findOverload(ns, name, decl);
        functionStack.push_back(name);
        functions[name]  = symbolTable[ns].internal + "* " + symbolTable[name].internal + "(";
        //functions[name] += decl.formPars.size() > 0 ? ", " : "";
        functions[name] += generateFormPars(name, decl.formPars) + ") {\n";
        functions[name] += symbolTable[ns].internal + "* this = malloc(sizeof(" + symbolTable[ns].internal + "));\n";
        functions[name] += symbolTable[ns].internal + "_init(this);\n";
        functions[name] += generateStatement(true, name, decl.statement);
        functions[name] += "return this;\n";
        functions[name] += "}";
        functionStack.pop_back();
        prototypes += symbolTable[ns].internal + "* " + symbolTable[name].internal + "(";
        //prototypes += decl.formPars.size() > 0 ? ", " : "";
        prototypes += generateFormPars(name, decl.formPars) + ");\n";
    } 
    
    ParseTree::Name initfunc = ns;
    ParseTree::Identifier oldi = initfunc.back();
    initfunc.pop_back();
    initfunc.push_back(ParseTree::Identifier(oldi.identifier + "_init"));
    ParseTree::Name destroyfunc = ns;
    ParseTree::Identifier oldd = destroyfunc.back();
    destroyfunc.pop_back();
    destroyfunc.push_back(ParseTree::Identifier(oldd.identifier + "_destroy"));
    functions[initfunc] += init;
    functions[destroyfunc] += destroy;
    
    return c;
}

